Passed
Push — feature/experience-step-functi... ( 062671...af7bb9 )
by Tristan
04:21
created

ReviewApplicationsRoot.updateReviewState   A

Complexity

Conditions 2

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 10
dl 0
loc 13
rs 9.9
c 0
b 0
f 0
1
/* eslint camelcase: "off", @typescript-eslint/camelcase: "off" */
2
import React from "react";
3
import ReactDOM from "react-dom";
4
5
// Internationalizations
6
import { injectIntl, defineMessages, WrappedComponentProps } from "react-intl";
7
8
import camelCase from "lodash/camelCase";
9
import Swal from "sweetalert2";
10
import {
11
  Job,
12
  Application,
13
  ReviewStatus,
14
  ApplicationReview,
15
} from "../../models/types";
16
import ReviewApplications from "./ReviewApplications";
17
import { find } from "../../helpers/queries";
18
import * as routes from "../../helpers/routes";
19
import { classificationString } from "../../models/jobUtil";
20
import { axios } from "../../api/base";
21
import RootContainer from "../RootContainer";
22
import { Portal } from "../../models/app";
23
import { ReviewStatusId } from "../../models/lookupConstants";
24
25
interface ReviewApplicationsProps {
26
  job: Job;
27
  initApplications: Application[];
28
  reviewStatuses: ReviewStatus[];
29
  portal: Portal;
30
}
31
32
interface ReviewApplicationsState {
33
  applications: Application[];
34
  savingStatuses: { applicationId: number; isSaving: boolean }[];
35
}
36
37
interface ReviewSubmitForm {
38
  review_status_id?: number | null;
39
  notes?: string | null;
40
}
41
42
const localizations = defineMessages({
43
  oops: {
44
    id: "review.applications.alert.oops",
45
    defaultMessage: "Save",
46
    description: "Dynamic Save button label",
47
  },
48
  somethingWrong: {
49
    id: "review.applications.reviewSaveFailed",
50
    defaultMessage:
51
      "Something went wrong while saving a review. Try again later.",
52
    description: "Dynamic Save button label",
53
  },
54
});
55
56
class ReviewApplicationsRoot extends React.Component<
57
  ReviewApplicationsProps & WrappedComponentProps,
58
  ReviewApplicationsState
59
> {
60
  public constructor(props: ReviewApplicationsProps & WrappedComponentProps) {
61
    super(props);
62
    this.state = {
63
      applications: props.initApplications,
64
      savingStatuses: props.initApplications.map(application => ({
65
        applicationId: application.id,
66
        isSaving: false,
67
      })),
68
    };
69
    this.handleStatusChange = this.handleStatusChange.bind(this);
70
    this.handleBulkStatusChange = this.handleBulkStatusChange.bind(this);
71
    this.handleNotesChange = this.handleNotesChange.bind(this);
72
    this.updateReviewState = this.updateReviewState.bind(this);
73
    this.handleSavingStatusChange = this.handleSavingStatusChange.bind(this);
74
  }
75
76
  protected updateReviewState(
77
    applicationId: number,
78
    review: ApplicationReview,
79
  ): void {
80
    const { applications } = this.state;
81
    const updatedApplications = applications.map(application => {
82
      if (application.id === applicationId) {
83
        return Object.assign(application, { application_review: review });
84
      }
85
      return { ...application };
86
    });
87
    this.setState({ applications: updatedApplications });
88
  }
89
90
  protected handleSavingStatusChange(
91
    applicationId: number,
92
    isSaving: boolean,
93
  ): void {
94
    const { savingStatuses } = this.state;
95
    const statuses = savingStatuses.map(item =>
96
      item.applicationId === applicationId
97
        ? { applicationId, isSaving }
98
        : { ...item },
99
    );
100
    this.setState({ savingStatuses: statuses });
101
  }
102
103
  protected submitReview(
104
    applicationId: number,
105
    review: ReviewSubmitForm,
106
  ): void {
107
    const { intl } = this.props;
108
    this.handleSavingStatusChange(applicationId, true);
109
    axios
110
      .put(routes.applicationReviewUpdate(intl.locale, applicationId), review)
111
      .then(response => {
112
        const newReview = response.data as ApplicationReview;
113
        this.updateReviewState(applicationId, newReview);
114
        this.handleSavingStatusChange(applicationId, false);
115
      })
116
      .catch(() => {
117
        Swal.fire({
118
          icon: "error",
119
          title: intl.formatMessage(localizations.oops),
120
          text: intl.formatMessage(localizations.somethingWrong),
121
        });
122
        this.handleSavingStatusChange(applicationId, false);
123
      });
124
  }
125
126
  protected handleStatusChange(
127
    applicationId: number,
128
    statusId: number | null,
129
  ): void {
130
    const { applications } = this.state;
131
    const application = find(applications, applicationId);
132
    if (application === null) {
133
      return;
134
    }
135
    const oldReview = application.application_review
136
      ? application.application_review
137
      : {};
138
    const submitReview = Object.assign(oldReview, {
139
      review_status_id: statusId,
140
    });
141
    this.submitReview(applicationId, submitReview);
142
  }
143
144
  protected handleBulkStatusChange(
145
    applicationIds: number[],
146
    statusId: number | null,
147
  ): void {
148
    const { applications } = this.state;
149
    const { intl } = this.props;
150
    const changedApplications = applications.filter(application =>
151
      applicationIds.includes(application.id),
152
    );
153
    let errorThrown = false;
154
    changedApplications.map(application => {
155
      const oldReview = application.application_review
156
        ? application.application_review
157
        : {};
158
      const submitReview = Object.assign(oldReview, {
159
        review_status_id: statusId,
160
      });
161
      this.handleSavingStatusChange(application.id, true);
162
      const request = axios
163
        .put(
164
          routes.applicationReviewUpdate(intl.locale, application.id),
165
          submitReview,
166
        )
167
        .then(response => {
168
          const newReview = response.data as ApplicationReview;
169
          this.updateReviewState(application.id, newReview);
170
          this.handleSavingStatusChange(application.id, false);
171
        })
172
        .catch(() => {
173
          this.handleSavingStatusChange(application.id, false);
174
          // Only show error modal first time a request fails
175
          if (!errorThrown) {
176
            errorThrown = true;
177
            Swal.fire({
178
              icon: "error",
179
              title: intl.formatMessage(localizations.oops),
180
              text: intl.formatMessage(localizations.somethingWrong),
181
            });
182
          }
183
        });
184
      return request;
185
    });
186
  }
187
188
  protected handleNotesChange(
189
    applicationId: number,
190
    notes: string | null,
191
  ): void {
192
    const { applications } = this.state;
193
    const application = find(applications, applicationId);
194
    if (application === null) {
195
      return;
196
    }
197
    const oldReview = application.application_review
198
      ? application.application_review
199
      : {};
200
    const submitReview = Object.assign(oldReview, {
201
      notes,
202
    });
203
    this.submitReview(applicationId, submitReview);
204
  }
205
206
  public render(): React.ReactElement {
207
    const { applications, savingStatuses } = this.state;
208
    const { reviewStatuses, job, portal, intl } = this.props;
209
210
    const reviewStatusOptions = reviewStatuses
211
      .filter(status => status.id in ReviewStatusId)
212
      .map(status => ({
213
        value: status.id,
214
        label: camelCase(status.name),
215
      }));
216
217
    return (
218
      <ReviewApplications
219
        jobId={job.id}
220
        title={job.title[intl.locale]}
221
        classification={classificationString(job)}
222
        closeDateTime={job.close_date_time}
223
        applications={applications}
224
        reviewStatusOptions={reviewStatusOptions}
225
        onStatusChange={this.handleStatusChange}
226
        onBulkStatusChange={this.handleBulkStatusChange}
227
        onNotesChange={this.handleNotesChange}
228
        savingStatuses={savingStatuses}
229
        portal={portal}
230
      />
231
    );
232
  }
233
}
234
235
const renderReviewApplications = (
236
  container: HTMLElement,
237
  portal: Portal,
238
): void => {
239
  if (
240
    container.hasAttribute("data-job") &&
241
    container.hasAttribute("data-applications") &&
242
    container.hasAttribute("data-review-statuses") &&
243
    container.hasAttribute("data-locale")
244
  ) {
245
    const job = JSON.parse(container.getAttribute("data-job") as string);
246
    const applications = JSON.parse(
247
      container.getAttribute("data-applications") as string,
248
    );
249
    const reviewStatuses = JSON.parse(
250
      container.getAttribute("data-review-statuses") as string,
251
    );
252
    const IntlReviewApplicationsRoot = injectIntl(ReviewApplicationsRoot);
253
    ReactDOM.render(
254
      <RootContainer>
255
        <IntlReviewApplicationsRoot
256
          job={job}
257
          initApplications={applications}
258
          reviewStatuses={reviewStatuses}
259
          portal={portal}
260
        />
261
      </RootContainer>,
262
      container,
263
    );
264
  }
265
};
266
267
if (document.getElementById("review-applications-container")) {
268
  const container = document.getElementById(
269
    "review-applications-container",
270
  ) as HTMLElement;
271
  renderReviewApplications(container, "manager");
272
}
273
274
if (document.getElementById("review-applications-container-hr")) {
275
  const hrContainer = document.getElementById(
276
    "review-applications-container-hr",
277
  ) as HTMLElement;
278
  renderReviewApplications(hrContainer, "hr");
279
}
280
281
export default injectIntl(ReviewApplicationsRoot);
282